<?php
/**
 * Класс, представляющий собой строку таблицы
 * @author Vadim Belyaev <vadimbelyaev@gmail.com>
 */
abstract class TableRow
{
    /**
     * @var string Имя класса, представляющего собой эту таблицу
     */
    protected $_table_class;

    /**
     * @var mixed ID строки
     */
    protected $_id = null;

    /**
     * @var array Поля строки
     */
    protected $_fields = array();

    /**
     * конструктор класса
     * @param ID mixed ID строки
     */
    public function __construct($id = null)
    {
        if ($id !== null) {
            $this->setId($id);
        }
    }

    /**
     * Устанавливает ID строки и считывает все поля этой строки.
     * После выполнения этого метода экземпляр класса становится
     * привязанным к конкретной строке таблицы.
     * @param $id mixed ID строки
     */
    public function setId($id)
    {
        $this->_id = $id;
        $this->_read();
    }

    /**
     * Проверяет, задан ли ID для этой строки.
     * @return bool
     */
    public function hasId()
    {
        return $this->_id !== null;
    }

    /**
     * Возвращает ID строки.
     * @return mixed
     */
    public function getId()
    {
        return $this->_id;
    }


    /**
     * Считывает значения всех полей.
     * Для вызова метода должен быть задан ID строки.
     * @throws ModelException
     */
    protected function _read()
    {
        //Если нет ID - нечего считывать
        if (!$this->hasId()) {
            throw new ModelException('Row ID is not set');
        }

        //Получаем единственный экземпляр класса БД
        $db    = MysqliDB::getInstance();

        //Класс таблицы
        $table = new $this->_table_class;

        //Выбираем все поля данной записи
        $query = sprintf('SELECT * FROM %s WHERE %s = %d',
                         $table->getTableName(),
                         $table->getPrimaryKey(),
                         $this->getId());
        $result = $db->query($query);
        if ($result && $result->num_rows) {
            //Запись существует - считываем ее
            $this->_fields = $result->fetch_assoc();
        } else {
            //Записи не существует - исключительная ситуация
            throw new ModelException(sprintf("Row with ID=%d doesn't exist", $this->getId()));
        }
    }

    /**
     * Магический метод. Вызывается при попытке присвоения значения
     * несуществующему свойству.
     * @param $name string
     * @param $value mixed
     */
    public function __set($name, $value)
    {
        $this->_fields[$name] = $value;
    }

    /**
     * Магический метод. Вызывается при попытке прочитать
     * несуществующее свойство.
     * @param $name string
     */
    public function __get($name)
    {
        if (array_key_exists($name, $this->_fields)) {
            return $this->_fields[$name];
        } else {
            return null;
        }
    }

    /**
     * Возвращает массив ссылок на элементы массива (для PHP 5.3 и выше)
     * @param $a array Одномерный массив
     * @return array
     * @author Ole Clausen
     * @see http://www.php.net/manual/en/mysqli-stmt.bind-param.php#106402
     */
    protected static function _getRefArray($a) {
        if (strnatcmp(phpversion(),'5.3') >= 0) {
            $ret = array();
            foreach($a as $key => $val) {
                $ret[$key] = &$a[$key];
            }
            return $ret;
        }
        return $a;
    } 

    /**
     * Сохранение (добавление или обновление записи)
     */
    public function save()
    {
        //Получаем названия, значения и типы полей
        $field_names = array();
        $field_values = array();
        $field_types = '';
        foreach ($this->_fields as $field_name => $field_value)
        {
            $field_names[] = $field_name;
            $field_values[] = $field_value;
            $field_types .= MysqliDB::getBindParamType($field_value);
        }

        //Получаем единственный экземпляр класса БД
        $db    = MysqliDB::getInstance();

        //Класс таблицы
        $table = new $this->_table_class;

        if ($this->hasId()) {
            //Существующая запись - выполняем обновление
            $field_expressions = array();
            foreach ($field_names as $field_name)
            {
                $field_expressions[] = $field_name . " = ?";
            }

            $query = sprintf("UPDATE %s SET %s WHERE %s = ?",
                             $table->getTableName(),
                             implode(", ", $field_expressions),
                             $table->getPrimaryKey());
            $stmt = $db->prepare($query);

            $field_types .= MysqliDB::getBindParamType($this->getId());

            $bind_param_args = array_merge(array($field_types), self::_getRefArray($field_values), self::_getRefArray(array($this->getId())));
            $method = new ReflectionMethod("mysqli_stmt", "bind_param");
            $method->invokeArgs($stmt, $bind_param_args);

            $result = $stmt->execute();
        } else {
            //Новая запись - выполняем добавление

            $query = sprintf("INSERT INTO %s (%s) VALUES (%s)",
                             $table->getTableName(),
                             implode(", ", $field_names),
                             implode(", ", array_fill(0, count($field_names), "?")));
            $stmt = $db->prepare($query);

            $bind_param_args = array_merge(array($field_types), self::_getRefArray($field_values));
            $method = new ReflectionMethod("mysqli_stmt", "bind_param");
            $method->invokeArgs($stmt, $bind_param_args);

            $result = $stmt->execute();
        }
    }

    /**
     * Удаление записи
     */
    public function delete()
    {
        //Если нет ID - нечего удалять
        if (!$this->hasId()) {
            throw new ModelException('Row ID is not set');
        }
        
        //Получаем единственный экземпляр класса БД
        $db    = MysqliDB::getInstance();

        //Класс таблицы
        $table = new $this->_table_class;

        //Запрос на удаление записи
        $query = sprintf(
                    "DELETE FROM %s WHERE %s = %d",
                    $table->getTableName(),
                    $table->getPrimaryKey(),
                    $this->getId()
                 );

        $db->query($query);
    }
}
